using System;
using System.Collections.Generic;

using Microsoft.WowAddonStudio.Lua.AST;
using Microsoft.WowAddonStudio.Lua.Model;

using Function = Microsoft.WowAddonStudio.Lua.AST.Function;

namespace Microsoft.WowAddonStudio.Lua.Adapters
{
    /// <summary>
    /// Provides a unified way to add the documentation for a <see cref="Node"/> in the AST to 
    /// its associated <see cref="DocumentationScope"/>.
    /// </summary>
    /// <typeparam name="TNode">The type of the node.</typeparam>
    public class DocumentationAdapter<TNode> : IDocumentationAdapter where TNode : Node
    {
        private static readonly Dictionary<Type, IDocumentationAdapter> adapters = new Dictionary<Type, IDocumentationAdapter>();
        private static readonly Dictionary<Type, Type> adapterTypes = InitializeAdapterTypes();
        private static Dictionary<Type, Type> InitializeAdapterTypes()
        {
            return new Dictionary<Type, Type> {
                                                  { typeof(Assignment), typeof(AssignmentDocumentationAdapter) },
                                                  { typeof(Function), typeof(FunctionDocumentationAdapter) },
                                                  { typeof(FunctionDeclaration), typeof(FunctionDeclarationDocumentationAdapter) },
                                                  { typeof(ForLoop), typeof(ForLoopDocumentationAdapter) },
                                                  { typeof(LocalDeclaration), typeof(LocalDeclarationDocumentationAdapter) }
                                              };
        }

        /// <summary>
        /// Adds the documentation for a node by recursing into its child nodes. This is the
        /// default implementation.
        /// </summary>
        /// <remarks>
        /// Implementers should override this method but not forget to call the base method
        /// as well for recursion.
        /// </remarks>
        /// <param name="documentationScope">The documentation scope.</param>
        /// <param name="node">The node to add documentation for.</param>
        public virtual void AddDocumentation(DocumentationScope documentationScope, TNode node)
        {
            if (documentationScope == null)
                throw new ArgumentNullException("documentationScope");
            if (node == null)
                throw new ArgumentNullException("node");


            // Iterate through all children that are still in the same scope and update their documentation
            foreach (var childNode in node.GetChildNodes())
            {
                if (childNode != null && childNode.Scope is DocumentationScope)
                    DocumentationAdapter<TNode>.AddDocumentation(childNode);
            }
        }

        void IDocumentationAdapter.AddDocumentation(Node node)
        {
            if (!(node.Scope is DocumentationScope))
                throw new ArgumentException("Node must be associated with a DocumentationScope.", "node");

            this.AddDocumentation((DocumentationScope)node.Scope, (TNode)node);
        }

        /// <summary>
        /// Adds the documentation for a node using the <see cref="DocumentationAdapter{TNode}"/> mapped
        /// to the type of the <see cref="Node"/>.
        /// </summary>
        /// <param name="node">The node to add documentation for.</param>
        public static void AddDocumentation(Node node)
        {
            if (node == null)
                throw new ArgumentNullException("node");
            if (!(node.Scope is DocumentationScope))
                throw new ArgumentException("Node must be associated with a DocumentationScope.", "node");

            Type nodeType = node.GetType();
            Type adapterType = adapterTypes.ContainsKey(nodeType) ? adapterTypes[nodeType] : typeof(DocumentationAdapter<Node>);

            if (!adapters.ContainsKey(adapterType))
                adapters[adapterType] = (IDocumentationAdapter)Activator.CreateInstance(adapterType);

            adapters[adapterType].AddDocumentation(node);
        }
    }
}